/*
 * Javlov - a Java toolkit for reinforcement learning with multi-agent support.
 * 
 * Copyright (c) 2009 Matthijs Snel
 * 
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */
package net.javlov.world.grid;

/**
 * A Grid represents a grid of GridCells. It presents methods for creating
 * the GridCells it contains, setting the neighbours of each cell, getting a
 * cell at a certain location in the grid, clearing the grid and moving
 * objects around in it.
 * 
 * @author University of Utrecht
 * @author Matthijs Snel
 * @version 0.2, 01/06/2007
 */
public class Grid
{
    /**
     * The border.
     */
	protected GridCell border;
    
	/**
     * The double array of GridCells.
     */
	protected GridCell cell[][];
    
	/**
	 * Grid width (nr of cells in rows) and height (nr of cells in columns).
	 */
    private int width,
    			height;
    
    private double cellWidth,
    				cellHeight;

    
    /**
     * Creates a Grid with width w and height h, using the given GridCellFactory.
     * 
     * @param w the width measured in nr of cells
     * @param h the height measured in nr of cells
     */
    public Grid(int w, int h, double cellWidth, double cellHeight ) {
    	width = w;
    	height = h;
    	this.cellWidth = cellWidth;
    	this.cellHeight = cellHeight;
    	border = new GridCell();
    	border.setBorder();
    	createGridCells();
        setNeighbours();
    }
    
    public void clear() {
    	for(int h = 0; h < height; h++)
    		for(int w = 0; w < width; w++) {
    			cell[w][h].clear();
    		}
    }
    
    /**
     * Creates the GridCells and a representation of the border of the grid.
     * 
     * @param factory the GridCellFactory used to construct the cells.
     * @see GridCellFactory
     */
    protected void createGridCells() {
    	cell = new GridCell[width][height];
    	for(int h = 0; h < height; h++)
    		for(int w = 0; w < width; w++) {
    			cell[w][h] = new GridCell(w*cellWidth, h*cellHeight, cellWidth, cellHeight);
    			if ( h == 0 || h == height - 1 || w == 0 || w == width - 1 )
    				cell[w][h].setBorder();
    		}
    }
    
    
    /**
     * Gets the cell at position (x,y).
     * 
     * @param x the x-coordinate.
     * @param y the y-coordinate
     * @return The GridCell at that position.
     * @see GridCell
     */
    public GridCell getCell(double x, double y) {
    	GridCell c = null;
    	try {
    		c = cell[ (int) (x/cellWidth) ][ (int) (y/cellHeight) ];
    	} catch (ArrayIndexOutOfBoundsException e) {
    		System.err.println("Pos " + x + "," + y + "; cell " + (int)(x/cellWidth) + "," + (int)(y/cellHeight) );
    		throw e;
    	}
    	return c;
    }

    /**
     * Starts at position (x,y) and gets the cell at position (x+dx, y+dy).
     * If the resulting position is outside of the grid, it checks whether the
     * grid is toroidal. If it's not, it returns the cell at the original position.
     * If it is, it returns the corresponding cell at the other side of the grid.
     * 
     * @param x starting x-coordinate.
     * @param y starting y-coordinate.
     * @param dx delta x
     * @param dy delta y
     * @return a GridCell
     * @see GridCell
     */
    public GridCell getCell(int x, int y, int dx, int dy) {
    	int x2 = x + dx;
    	int y2 = y + dy;

    	if(x2 < 0 || x2 >= width || y2 < 0 || y2 >= height) {
           //	return cell[x][y];
    		return border;
        }
        else 
        	return cell[x2][y2];
    }
    
    /**
     * Starts at position (x,y) and gets the cell at position (x+dx, y+dy).
     * If the resulting position is outside of the grid, it checks whether the
     * grid is toroidal. If it's not, it returns the cell at the original position.
     * If it is, it returns the corresponding cell at the other side of the grid.
     * 
     * @param x starting x-coordinate.
     * @param y starting y-coordinate.
     * @param dx delta x
     * @param dy delta y
     * @return a GridCell
     * @see GridCell
     */
    public GridCell getCell(double x, double y, int dx, int dy) {
    	return getCell( (int) (x/cellWidth), (int) (y/cellHeight) );
    }
    
    public GridCell[][] getCells() {
    	return cell;
    }
    
    public double getCellHeight() {
    	return cellHeight;
    }
    
    public double getCellWidth() {
    	return cellWidth;
    }
    
    public int getHeight() {
    	return width;
    }
    
    public int getSize() {
    	return width*height;
    }
    
    public int getWidth() {
    	return width;
    }
    
    /**
     * Sets the neighbouring fields of a field
     */
    public void setNeighbours() {
        for(int w = 0; w < width; w++)
            for(int h = 0; h < height; h++)
            	for(int d = 0; d < 8; d++)
            		cell[w][h].setNeighbour(d, getNeighbour(w,h,Direction.all[d]));
    }
    
    /**
     * Gets the neighbouring field in a particular direction taken in as the last
     * parameter. The two first parameters are the position of the field in the
     * grid.
     */
    public GridCell getNeighbour(int h, int v, Direction d) {	
    	return getCell(h, v, d.x(), d.y());
    }   
}
